require_relative 'command_id'
require_relative 'command_status'
require_relative 'pdu_error'
require_relative 'tlv/tlv_factory'
require_relative '../util/byte_buffer'

module SmppClient

  module Pdu

    # Super class for all PDU types. Subclass should override Pdu#body method.
    #
    # Note: _command_length_ attribute may contain wrong value.
    # To get actual length call Pdu#buffer first.

    class Pdu

      include Tlv
      include SmppClient::Util

      # PDU mandatory header length in octet bytes.
      HEADER_LENGTH = 16

      attr_reader :command_length, :command_id, :tlvs
      attr_accessor :command_status, :sequence_number

      # There is two ways to create Pdu object:
      # 1. Pdu.new(ByteBuffer) - parse bytes and return pdu or raise error
      # 2. Pdu.new(CommandId) - should be used only in subclasses
      def initialize(arg)
        @tlvs = Hash.new
        if arg.kind_of? ByteBuffer
          parse_header(arg)
        else
          @command_id = arg
          @command_length = HEADER_LENGTH
          @command_status = CommandStatus::ESME_ROK
          @sequence_number = 0
        end
      end

      # Return octet byte buffer for this Pdu..
      def buffer
        body = body_bytes
        tlv = tlv_bytes
        @command_length = HEADER_LENGTH + body.size + tlv.size
        header.append_bytes(body).append_bytes(tlv)
      end

      def to_s
        "command length: #{@command_length}, command id: #{@command_id}, command status: #{@command_status}, sequence number: #{@sequence_number}"
      end

      protected

      # Return body  bytes..
      def body_bytes
        return []
      end

      private

      # Parse PDU header from octet byte buffer.
      def parse_header(byte_buffer)
        raise PduError, 'PDU should be at least 16 octets length.' if byte_buffer.size < HEADER_LENGTH
        @command_length = byte_buffer.read_int
        raise PduError, "PDU #{byte_buffer.size} octets length, but #{@command_length} should be." if @command_length != byte_buffer.size
        byte_buffer.remove_int
        @command_id = byte_buffer.remove_int
        @command_status = byte_buffer.remove_int
        @sequence_number = byte_buffer.remove_int
      end

      # Return octet byte buffer for Pdu header.
      def header
        head_buffer = ByteBuffer.new
        head_buffer.append_int(@command_length)
        head_buffer.append_int(@command_id)
        head_buffer.append_int(@command_status)
        head_buffer.append_int(@sequence_number)
      end

      def parse_tlvs(byte_buffer)
        while byte_buffer.size > 0
          tlv = TlvFactory.parse(byte_buffer)
          @tlvs[tlv.tag] = tlv
        end
      end

      def tlv_bytes
        tlv_buffer = ByteBuffer.new
        @tlvs.each_value do |tlv|
          tlv_buffer.append_bytes(tlv.buffer.bytes)
        end
        return tlv_buffer.bytes
      end

    end

  end

end